// ｜---｜ 1. Compose

function compose(...fn) {
  if (!fn.length) return (v) => v
  if (fn.length === 1) return fn[0]

  //   let i = 0;
  //   let re = (...args) => fn[i](...args);

  //   while (i < fn.length) {
  //     i += 1;
  //     re = re((...args) => fn[i](...args));
  //   }

  //   return re;

  return fn.reduce(
    (pre, cur) =>
      (...args) =>
        pre(cur(...args))
  )
}

// 用法如下:
function fn1(x) {
  return x + 1
}
function fn2(x) {
  return x + 2
}
function fn3(x) {
  return x + 3
}
function fn4(x) {
  return x + 4
}
const a = compose(fn1, fn2, fn3, fn4)
console.log(a(1)) // 1+4+3+2+1=11

// fn1(fn2(fn3(fn4(x))))(1)

//  ｜---｜ 2. settimeout 实现 setinterval

function mytimeout(fn, delay) {
  let timer = null
  function interval() {
    fn()
    timer = setTimeout(interval, delay)
  }

  interval()

  return {
    cancel: () => {
      clearTimeout(timer)
      timer = null
    },
  }
}

let b = mytimeout(() => {
  console.log('a')
}, 1000)

b.cancel()

//  ｜---｜ 3. 发布订阅模式

class EventEmitter {
  constructor() {
    this.events = new Map() // map set {} []
  }

  on(type, cb) {
    if (!this.events.has(type)) {
      this.events.set(type, [cb])
    } else {
      this.events.get(type).push(cb)
    }
  }
  off(type, cb) {
    if (!this.events.has(type)) return
    const result = this.events.get(type).filter((item) => {
      return item !== cb
    })
    this.events.set(type, result)
  }

  emit(type, ...rest) {
    if (this.events.has(type)) {
      this.events.get(type).forEach((event) => {
        event.call(this, ...rest)
      })
    }
  }

  once(type, cb) {
    const wrapper = () => {
      cb()
      this.off(type, wrapper)
    }
    this.on(type, wrapper)
  }
}

//  ｜---｜ 4. 数组去重
function uniqueArr(arr) {
  return [...new Set(arr)]
}
//  ｜---｜ 5. 数组扁平化

function flatter(arr) {
  if (!arr.length) return
  return arr.reduce(
    (pre, cur) =>
      Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur],
    []
  )
}

function flatter(arr) {
  if (!arr.length) return
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr)
  }

  return arr
}

//  ｜---｜ 6. 继承
// 原型链继承 构造函数继承 组合继承 实例继承 拷贝继承 寄生组合继承
// 原型链继承 + 构造函数继承 = 组合继承
// 组合继承 - 多余的父类实例 = 寄生组合继承

// 下面写的是伪代码

// 原型链继承   Son.prototype = new Father();
// 优点：
// 1. 简单，易于实现
// 2. 父类新增原型方法、原型属性，子类都能访问到
// 缺点：
// 1. 无法实现多继承，因为原型一次只能被一个实例更改
// 2. 来自原型对象的所有属性被所有实例共享（上诉例子中的color属性）
// 3. 创建子类实例时，无法向父构造函数传参

// 构造函数继承 Father.call(this, '传给父类的参数')
// 优点：
// 1. 解决了原型链继承中子类实例共享父类引用属性的问题
// 2. 创建子类实例时，可以向父类传递参数
// 3. 可以实现多继承（call多个父类对象）
// 缺点：
// 1. 实例并不是父类的实例，只是子类的实例
// 2. 只能继承父类实例的属性和方法，不能继承其原型上的属性和方法
// 3. 无法实现函数复用，每个子类都有父类实例函数的副本，影响性能

// 组合继承
// 优点：
// 1. 弥补了构造继承的缺点，现在既可以继承实例的属性和方法，也可以继承原型的属性和方法
// 2. 既是子类的实例，也是父类的实例
// 3. 可以向父类传递参数
// 4. 函数可以复用
// 缺点：
// 1. 调用了两次父类构造函数，生成了两份实例
// 2. constructor指向问题

// 实例继承
function Son(name) {
  let r = new Father()
  r.name = name
  return r
}
// 优点：
// 1. 不限制调用方式，不管是new 子类()还是子类(),返回的对象具有相同的效果
// 缺点：
// 1. 实例是父类的实例，不是子类的实例
// 2. 不支持多继承

// 拷贝继承
function Son(name) {
  let f = new Father('传给父类的参数')
  for (let i in f) {
    this.prototype[i] = f[i]
  }
  this.name = name
}
// 优点：
// 1. 支持多继承
// 缺点：
// 1. 效率低，性能差，占用内存高（因为需要拷贝父类属性）
// 2. 无法获取父类不可枚举的方法（不可枚举的方法，不能使用for-in访问到)

// 寄生组合
function Son(name) {
  Father.call(this, '传给父类的参数')
  this.name = name
}

Son.prototype = Object.create(Parent.prototype)
Son.prototype.constructor = Son
// 上面两行可以这么写
;(function () {
  let f = function () {}
  f.prototype = Parent.prototype
  Son.prototype = new f()
  Son.prototype.constructor = Son
})()
// 优点：
// 1. 比较完美（js实现继承首选方式）
// 缺点：
// 1.实现起来较为复杂（可通过Object.create简化）

// 什么叫做多继承 ？ 多层继承可以理解
// 本人觉得多继承没有必要
// 或者说继承也没有必要
// 用组合，函数式编程思想来解决问题更好

/*
  ｜__｜ 插入篇 js 防抖 节流 event loop 取消Promise
*/
// 防抖：动作停止后的时间超过设定时间执行一次函数
function debounce(fn, delay) {
  let timer = null
  return (...args) => {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.call(this, ...args)
    }, delay)
  }
}
// 节流：一定时间内执行的操作只执行一次
// 设置标志
function throttle(fn, delay) {
  let flag = true
  return () => {
    if (!flag) return
    flag = false
    timer = setTimeout(() => {
      fn()
      flag = true
    }, delay)
  }
}
// 使用时间戳
function throttle(fn, delay) {
  let startTime = new Date()
  return () => {
    let endTime = new Date()
    if (endTime - startTime >= delay) {
      fn()
      startTime = endTime
    } else {
      return
    }
  }
}

// 事件循环 与 vue.nextTick

// 取消 Promise
// 方法1 promise.race()
function wrap(p) {
  let obj = {}
  let p1 = new Promise((resolve, reject) => {
    obj.resolve = resolve
    obj.reject = reject
  })
  obj.promise = Promise.race([p1, p])
  return obj
}
let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(234)
  }, 1000)
})
let obj = wrap(promise)
obj.promise.then((res) => {
  console.log(res)
})
obj.resolve('请求被拦截了')
obj.reject('请求被拒绝了')
// 方法2 包装一个可控的 promise
function wrap(p) {
  let res = null
  let abort = null
  let p1 = new Promise((resolve, reject) => {
    res = resolve
    abort = reject
  })
  p1.abort = abort
  p.then(res, abort)
  return p1
}
let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(234)
  }, 1000)
})

let obj = wrap(promise)
obj.then((res) => {
  console.log(res)
})
obj.abort('请求被拦截了')

// ｜---｜ 7. Promise 编程思想
// 取消 promise promise手写 并行限制的 promise
// 并行限制的 promise
class Scheduler {
  constructor(limit) {
    this.queue = []
    this.maxCount = limit
    this.runCounts = 0
  }
  add(time, order) {
    const promiseCreator = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(order)
          resolve()
        }, time)
      })
    }
    this.queue.push(promiseCreator)
  }
  taskStart() {
    for (let i = 0; i < this.maxCount; i++) {
      this.request()
    }
  }
  request() {
    if (!this.queue || !this.queue.length || this.runCounts > this.maxCount)
      return
    this.runCounts += 1
    this.queue
      .shift()()
      .then(() => {
        this.runCounts -= 1
        this.request()
      })
  }
}
const scheduler = new Scheduler(2)
const addTask = (time, order) => {
  scheduler.add(time, order)
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')
scheduler.taskStart()

// Promise

class MyPromise {
  constructor(fn) {
    this.state = 'pending'
    this.successFun = []
    this.failFn = []
    let resolve = (val) => {
      if (this.state !== 'pending') return
      this.state = 'success'
      setTimeout(() => {
        this.successFun.forEach((item) => item.call(this, val))
      })
    }
    let reject = (err) => {
      if (this.state !== 'pending') return
      this.state = 'fail'
      setTimeout(() => {
        this.failFn.forEach((item) => item.call(this, err))
      })
    }
    try {
      fn(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  then(resolveCb, rejectCb) {
    resolveCb = typeof resolveCb !== 'function' ? (v) => v : resolveCb
    rejectCb =
      typeof rejectCb !== 'function'
        ? (err) => {
            throw err
          }
        : rejectCb
    return new MyPromise((resolve, reject) => {
      this.successFun.push((val) => {
        try {
          let x = resolveCb(val)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      })
      this.failFn.push((val) => {
        try {
          let x = rejectCb(val)
          x instanceof MyPromise ? x.then(resolve, reject) : reject(x)
        } catch (error) {
          reject(error)
        }
      })
    })
  }

  static all(promiseArr) {
    let result = []
    let count = 0
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
        Promise.resolve(promiseArr[i]).then(
          (res) => {
            result[i] = res
            count++
            if (count === promiseArr.length) {
              resolve(result)
            }
          },
          (err) => {
            reject(err)
          }
        )
      }
    })
  }

  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
        Promise.resolve(promiseArr[i]).then(
          (res) => {
            resolve(res)
          },
          (err) => {
            reject(err)
          }
        )
      }
    })
  }
}

// new 手写

function myNew(fn) {
  let obj = Object.create(fn.prototype)

  let result = fn.call(this, ...arguments)

  if (result && (typeof result === 'function' || typeof result === 'object')) {
    return result
  }

  return obj
}

// call apply bind
Function.prototype.myCall = function (context, ...args) {
  if (!context || context === null) context = window
  let fn = Symbol()
  context[fn] = this
  return context[fn](...args)
}

Function.prototype.myApply = function (context, ...args) {
  if (!context || context === null) context = window
  let fn = Symbol()
  context[fn] = this
  return context[fn](args)
}

Function.prototype.myBind = function (context, ...args) {
  if (!context || context === null) context = window
  let fn = Symbol()
  context[fn] = this
  let _this = this
  const result = function (...innerArgs) {
    if (this instanceof _this) {
      this[fn] = _this
      this[fn](...[...args, ...innerArgs])
      delete this[fn]
    } else {
      context[fn](...[...args, ...innerArgs])
      delete context[fn]
    }
  }

  result.prototype = Object.create(this.prototype)
  return result
}

// 深拷贝
function isObject(val) {
  return (typeof val === 'object' || typeof val === 'function') && val !== null
}

function deepClone(obj, hash = new WeakMap()) {
  if (!isObject(obj)) return obj
  if (hash.has(obj)) return hash.get(obj)
  let target = Array.isArray(obj) ? [] : {}
  hash.set(obj, target)
  for (let k of Reflect.ownKeys(obj)) {
    if (isObject(obj[k])) {
      target[k] = deepClone(obj[k], hash)
    } else {
      target[k] = obj[k]
    }
  }
  return target
}
// instanceof
function myInstanceof(left, right) {
  while (true) {
    if (left === null) return false
    if (left.__proto__ === right.prototype) return true
    left = left.__proto__
  }
}
// curring
function curring(a) {
  return (b) => (c) => a + b + c
}

var a = curr(1)
var b = a(2)
var c = b(3)
console.log(c)

function currying(fn, ...args) {
  const res = (...newArgs) => {
    if ([...args, ...newArgs].length === fn.length) {
      return fn(...args, ...newArgs)
    } else {
      return res
    }
  }
  return res
}

var curr = curring((a, b, c) => a + b + c)

// 冒泡

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length
  // 外层循环用于控制从头到尾的比较+交换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于完成每一轮遍历过程中的重复比较+交换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素前面的数比后面的大
      if (arr[j] > arr[j + 1]) {
        // 交换两者
        ;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
      }
    }
  }
  // 返回数组
  return arr
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

// 选择排序
function selectSort(arr) {
  const len = arr.length
  for (let i = 0; i < len - 1; i++) {
    let minIndex = i
    for (let j = i; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    if (minIndex !== i) {
      ;[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
    }
  }
  return arr
}
// 插入排序
function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    let j = i
    let target = arr[j]
    while (j > 0 && arr[j - 1] > target) {
      arr[j] = arr[j - 1]
      j--
    }
    arr[j] = target
  }
  return arr
}
// 快速排序
function quickSort(arr) {
  if (arr.length < 2) return arr
  const cur = arr[arr.length - 1]
  const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1)
  const right = arr.filter((v) => v > cur)
  return [...quickSort(left), cur, ...quickSort(right)]
}
// 归并排序
function merge(left, right) {
  let i = 0
  let j = 0
  let arr = []
  while (i < left.length && j < right.length) {
    if (left[i] < right[j]) {
      arr.push(left[i])
      i += 1
    } else {
      arr.push(right[j])
      j += 1
    }
  }
  if (i < left.length) {
    arr.push(...left.slice(i))
  } else {
    arr.push(...right.slice(j))
  }
  return arr
}

function mergeSort(arr) {
  if (arr.length < 2) return arr
  const mid = Math.floor(arr.length / 2)
  const left = mergeSort(0, mid)
  const right = mergeSort(arr.slice(mid))
  return merge(left, right)
}

// 二分查找
// 如何确定一个数在一个有序数组中的位置

function search(arr, target, start, end) {
  let targetIndex = -1

  if (start > end) {
    return targetIndex
  }

  const mid = Math.floor((start + end) / 2)

  if (arr[mid] === target) {
    targetIndex = mid
    return targetIndex
  }

  if (arr[mid] < target) {
    return search(arr, target, mid + 1, end)
  } else {
    return search(arr, target, start, mid - 1)
  }
}

// lazyMan
// 用函数来保存动作的执行空间
// 用数组 push/unshift  shift 来实现队列操作
// 每次执行完，自动开始下次执行
// 首次执行，创建一个宏任务
class LazyMan {
  constructor(name) {
    this.tasks = []
    const task = () => {
      console.log('Hi! This is ' + name + '!')
      this.next()
    }
    this.tasks.push(task)
    setTimeout(() => {
      this.next()
    }, 0)
  }
  next() {
    const task = this.tasks.shift()
    task && task()
  }
  sleep(time) {
    const task = () => {
      setTimeout(() => {
        console.log('Wake up after ' + time)
        this.next()
      }, time * 1000)
    }
    this.tasks.push(task)
    return this
  }
  eat(food) {
    const task = () => {
      console.log('Eat ' + food + '~')
      this.next()
    }
    this.tasks.push(task)
    return this
  }
  sleepFirst(time) {
    const task = () => {
      setTimeout(() => {
        console.log('Wake up after ' + time)
        this.next()
      }, time * 1000)
    }
    this.tasks.unshift(task)
    return this
  }
}

function lazyMan(name) {
  return new LazyMan(name)
}
lazyMan('Hank').eat('supper').sleepFirst(5)

// 版本号排序方法
// 题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.4', '4.3.4.5']。
// 现在需要对其进行排序，排序的结果为 ['4.3.4.5', '4.3.4', '4.2', '2.3.3', '0.302.1', '0.1.1']

var arr = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.4', '4.3.4.5']
arr = arr.sort((a, b) => {
  let i = 0
  const arr1 = a.split('.')
  const arr2 = b.split('.')
  while (true) {
    const s1 = arr1[i]
    const s2 = arr2[i]
    i++
    if (s1 === void 0 || s2 === void 0) {
      return arr2.length - arr1.length
    }
    if (s1 === s2) continue
    return s2 - s1
  }
})

// LRU cache
class LRUCache {
  constructor(capacity) {
    this.secretKey = new Map()
    this.capacity = capacity
  }
  get(key) {
    if (this.secretKey.has(key)) {
      let tempVal = this.secretKey.get(key)
      this.secretKey.delete(key)
      this.secretKey.set(key, tempVal)
      return tempVal
    } else {
      return -1
    }
  }
  put(key, val) {
    if (this.secretKey.has(key)) {
      this.secretKey.delete(key)
      this.secretKey.set(key, val)
    } else if (this.secretKey.size < this.capacity) {
      this.secretKey.set(key, val)
    } else {
      this.secretKey.set(key, val)
      this.secretKey.delete(this.secretKey.keys().next().value)
    }
  }
}
// curring
function add(...args) {
  let all = [...args]
  function fn(...newargs) {
    all = [...all, ...newargs]
    return fn
  }

  fn.toString = function () {
    if (!all.length) return
    return all.reduce((sum, cur) => sum + cur, 0)
  }
  return fn
}

// 动态规划求解硬币找零问题

// DOM2JSON
function dom2json(domtree) {
  let obj = {}
  obj.name = domtree.tagName
  obj.children = []
  domtree.childNodes.forEach((child) => obj.children.push(dom2json(child)))
  return obj
}
// html 字符串解析成 虚拟dom的做法呢？

// 类数组转化为数组的方法
const arrayLike = document.querySelectorAll('div')
// [...arrayLike]
Array.from(arrayLike)
Array.prototype.slice.call(arrayLike)
Array.apply(null, arrayLike)
Array.prototype.concat.apply([], arrayLike)

//                NaN === NaN    +0 === -0
// Object.is()       true           false
// ===               false           true
Object.prototype.is = function (x, y) {
  if (x === y) {
    return x !== 0 || 1 / x === 1 / y
  }
  return x !== x && y !== y
}

// Ajax
const getJson = function (url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url, false)
    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.onreadystatechange = function () {
      if (xhr.readyState !== 4) return
      if (xhr.status === 200 || xhr.status === 304) {
        resolve(xhr.responseText)
      } else {
        reject(new Error(xhr.responseText))
      }
    }
    xhr.send()
  })
}
// 分片思想 解决大文件上传或者大数据渲染
let ul = document.getElementById('container')
let total = 100000
let once = 20
let page = total / once
let index = 0
function loop(curTotal, curIndex) {
  if (curTotal <= 0) return false
  let pageCount = Math.min(curTotal, once)
  window.requestAnimationFrame(function () {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement('li')
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
      ul.appendChild(li)
    }
    loop(curTotal - pageCount, curIndex + pageCount)
  })
}
loop(total, index)

// 虚拟dom转化为真实dom
// {
//   tag: 'DIV',
//   attrs:{
//   id:'app'
//   },
//   children: [
//     {
//       tag: 'SPAN',
//       children: [
//         { tag: 'A', children: [] }
//       ]
//     },
//     {
//       tag: 'SPAN',
//       children: [
//         { tag: 'A', children: [] },
//         { tag: 'A', children: [] }
//       ]
//     }
//   ]
// }
// 把上诉虚拟Dom转化成下方真实Dom
// <div id="app">
//   <span>
//     <a></a>
//   </span>
//   <span>
//     <a></a>
//     <a></a>
//   </span>
// </div>

function render(vnode) {
  if (typeof vnode === 'number') {
    vnode = String(vnode)
  }
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode)
  }
  const dom = document.createElement(vnode.tag)
  if (vnode.attrs) {
    Object.keys(vnode.attrs).forEach((key) => {
      const value = vnode.attrs[key]
      dom.setAttribute(key, value)
    })
  }
  vnode.children.forEach((child) => {
    dom.appendChild(render(child))
  })
  return dom
}
// 模板字符串解析
let str = '我是{{name}}，年龄{{age}}，性别{{sex}}'
let data = {
  name: '姓名',
  age: 18,
}

function _template(template, data) {
  return template.replace(/\{\{(\w+)\}\}/g, function (match, key) {
    return data[key]
  })
}
const result = _template(str, data)
// 对象flattern
const obj = {
  a: {
    b: 1,
    c: 2,
    d: { e: 5 },
  },
  b: [1, 3, { a: 2, b: 3 }],
  c: 3,
}

function isObject(val) {
  return typeof val === 'object' && val !== null
}
function flatter(obj) {
  if (!isObject(obj)) return
  let res = {}
  const dfs = (cur, prefix) => {
    if (isObject(cur)) {
      if (Array.isArray(cur)) {
        cur.forEach((item, index) => {
          dfs(item, `${prefix}[${index}]`)
        })
      } else {
        for (let k in cur) {
          dfs(cur[k], `${prefix}${prefix ? '.' : ''}${k}`)
        }
      }
    } else {
      res[prefix] = cur
    }
  }
  dfs(obj, '')
  return res
}
flatter(obj)

// 列表转树结构
// id parentId children 这三个属性名是必须已知的且必填
function listToTree(list) {
  let temp = {}
  let treeData = []
  for (let i = 0; i < list.length; i++) {
    temp[list[i].id] = list[i]
  }
  for (let k in temp) {
    if (temp[k].parentId !== 0) {
      if (temp[temp[k].parentId]) {
        // 假设能够找到子节点对应的父节点
        if (!temp[temp[k].parentId].children) {
          temp[temp[k].parentId].children = []
        }
        temp[temp[k].parentId].children.push(temp[k])
      } else {
        // 静默处理
        // 没有找到父节点，就忽略掉这个子节点以及子节点下面的子树
      }
    } else {
      treeData.push(temp[k])
    }
  }
  return treeData
}

// 树转列表
function treeToList(tree) {
  let res = []
  const dfs = (tree) => {
    tree.forEach((item) => {
      if (item.children) {
        dfs(item.children)
        delete item.children
      }
      res.push(item)
    })
  }
  dfs(tree)
  return res
}

// 大数相加
let a = '0009007199254740992'
let b = '1234567899999999999'

function add(a, b) {
  let maxLength = Math.max(a.length, b.length)
  a = a.padStart(maxLength, 0)
  b = b.padStart(maxLength, 0)

  let t = 0
  let f = 0
  let sum = ''
  for (let i = maxLength - 1; i >= 0; i--) {
    t = parseInt(a[i]) + parseInt(b[i]) + f
    f = Math.floor(t / 10)
    sum = (t % 10) + sum
  }
  if (f !== 0) {
    sum = '' + f + sum
  }
  return sum
}
